﻿#include "application.h"

#pragma comment(lib, "gdiplus.lib")

#define APPLICATION_CLASS_NAME L"MainWindow"

namespace xy
{

namespace app
{

using namespace xy::geom;

application::application(HINSTANCE hInstance, int nCmdShow, int x, int y, int width, int height, const wchar_t *title)
{
#ifndef NDEBUG
    // 在 Windows 平台上显示控制台
    AllocConsole();
    FILE *pCout;
    freopen_s(&pCout, "CONOUT$", "w", stdout);
    freopen_s(&pCout, "CONOUT$", "w", stderr);

    // 设置控制台代码页为 UTF-8
    SetConsoleOutputCP(CP_UTF8);
#endif

    // 初始化 GDI+
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

    // 注册窗口类
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = application::wndproc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = APPLICATION_CLASS_NAME;
    wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wcex);

    // 创建窗口
    m_hWnd = CreateWindow(APPLICATION_CLASS_NAME, title, WS_OVERLAPPEDWINDOW, x, y, width + 16, height + 39, NULL, NULL,
                          hInstance, NULL);

    SetProp(m_hWnd, APPLICATION_CLASS_NAME, this);
    ShowWindow(m_hWnd, nCmdShow);
    UpdateWindow(m_hWnd);
}

application::~application()
{
    RemoveProp(m_hWnd, APPLICATION_CLASS_NAME);
    DestroyWindow(m_hWnd);
}

int application::message_loop()
{
    // 定时器，60 帧每秒
    SetTimer(m_hWnd, 1, 1000 / 60, NULL);

    // 主消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // 销毁定时器
    KillTimer(m_hWnd, 1);

    // 在关闭 GDI+ 前释放位图
    m_bitmap.reset();

    // 关闭 GDI+
    Gdiplus::GdiplusShutdown(m_gdiplusToken);

#ifndef NDEBUG
    // 释放控制台
    FreeConsole();
#endif
    return (int)msg.wParam;
}

void application::paint_event()
{
    if (m_renderContext == nullptr)
        return;

    // 渲染图像
    m_renderContext->draw();
    auto image = m_renderContext->painter()->frame;

    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(m_hWnd, &ps);

    // 使用 GDI+ 绘制 PNG 图像
    Gdiplus::Graphics graphics(hdc);

    // 锁定位图区域
    Gdiplus::BitmapData bitmapData;
    Gdiplus::Rect rect(0, 0, image->width, image->height);
    m_bitmap->LockBits(&rect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bitmapData);

    // 直接复制内存数据
    std::memcpy(bitmapData.Scan0, image->data.data(), image->width * image->height * image->bpp);

    // 解锁位图
    m_bitmap->UnlockBits(&bitmapData);

    // 绘制位图
    RECT bound;
    GetWindowRect(m_hWnd, &bound);

    int width = bound.right - bound.left - 16;
    int height = bound.bottom - bound.top - 39;
    graphics.DrawImage(m_bitmap.get(), width * 0.05f, height * 0.05f, m_bitmap->GetWidth(), m_bitmap->GetHeight());

    EndPaint(m_hWnd, &ps);
}

void application::resize_event()
{
    RECT rect;
    GetWindowRect(m_hWnd, &rect);

    int width = rect.right - rect.left - 16;
    int height = rect.bottom - rect.top - 39;

    if (m_renderContext)
    {
        m_renderContext->painter()->resize(width * 0.9f, height * 0.9f);
        m_bitmap = std::make_shared<Gdiplus::Bitmap>(width * 0.9f, height * 0.9f, PixelFormat32bppARGB);
    }
}

void application::mouse_event(UINT message, WPARAM wParam, LPARAM lParam)
{
    if (m_aisContext)
    {
        switch (message)
        {
        case WM_MOUSEWHEEL: {
            short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
            m_aisContext->mouse_wheel_event(wheelDelta / 120.0f);
            break;
        }
        case WM_MOUSEMOVE: {
            float x = LOWORD(lParam);
            float y = HIWORD(lParam);
            m_aisContext->mouse_move_event(vec2f{x, y});
            break;
        }
        case WM_RBUTTONDOWN: {
            float x = LOWORD(lParam);
            float y = HIWORD(lParam);
            m_aisContext->mouse_press_event(vec2f{x, y}, ais::msg_mouse::Button::Right);
            break;
        }
        case WM_LBUTTONDOWN: {
            float x = LOWORD(lParam);
            float y = HIWORD(lParam);
            m_aisContext->mouse_press_event(vec2f{x, y}, ais::msg_mouse::Button::Left);
            break;
        }
        case WM_MBUTTONDOWN: {
            float x = LOWORD(lParam);
            float y = HIWORD(lParam);
            m_aisContext->mouse_press_event(vec2f{x, y}, ais::msg_mouse::Button::Middle);
            break;
        }
        case WM_RBUTTONUP:
        case WM_LBUTTONUP:
        case WM_MBUTTONUP: {
            float x = LOWORD(lParam);
            float y = HIWORD(lParam);
            m_aisContext->mouse_release_event(vec2f{x, y});
            break;
        }
        default:
            break;
        }
    }
}

void application::key_event(UINT message, WPARAM wParam, LPARAM lParam)
{
    // wParam 中存储了按键的虚拟键码
    int key = wParam;
    m_aisContext->key_event(key);

    switch (key)
    {
    case 'S': {
        // 保存图像
        std::string filename = TEST_DATA_PATH "screenshot.png";
        m_renderContext->painter()->write_image(filename);
        break;
    }
    default: {
        break;
    }
    }
}

// 窗口过程函数的实现
LRESULT CALLBACK application::wndproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    application *app = (application *)GetProp(hWnd, APPLICATION_CLASS_NAME);
    switch (message)
    {
    case WM_PAINT: {
        app->paint_event();
        break;
    }
    case WM_SIZE: {
        app->resize_event();
        break;
    }
    case WM_MOUSEMOVE:
    case WM_MOUSEWHEEL:
    case WM_RBUTTONDOWN:
    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_LBUTTONUP:
    case WM_MBUTTONUP: {
        app->mouse_event(message, wParam, lParam);
        break;
    }
    case WM_KEYDOWN:
    case WM_KEYUP: {
        app->key_event(message, wParam, lParam);
        break;
    }
    case WM_TIMER: {
        // 定时器事件，重新渲染图像
        InvalidateRect(hWnd, NULL, FALSE);
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

} // namespace app

} // namespace xy
